package cn.tedu.charging.order.service.impl;

import cn.tedu.charging.order.common.ChargingConstants;
import cn.tedu.charging.order.common.MqttConstants;
import cn.tedu.charging.order.feign.DeviceClient;
import cn.tedu.charging.order.feign.UserClient;
import cn.tedu.charging.order.mqtt.MqttProducer;
import cn.tedu.charging.order.pojo.dto.ChargingDto;
import cn.tedu.charging.order.pojo.param.OrderAddParam;
import cn.tedu.charging.order.pojo.po.OrderMQPO;
import cn.tedu.charging.order.quartz.DeviceCheckJob;
import cn.tedu.charging.order.rabbit.RabbitMQOrderProducer;
import cn.tedu.charging.order.service.OrderService;
import cn.tedu.charing.common.pojo.JsonResult;
import cn.tedu.charing.common.pojo.param.GunStatusUpdateParam;
import cn.tedu.charing.common.pojo.vo.StationInfoVO;
import cn.tedu.charing.common.pojo.vo.UserInfoVO;
import cn.tedu.charing.common.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Random;

//静态导入 但是不推荐


@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired private UserClient userClient;

    @Autowired
    private DeviceClient deviceClient;

    /**
     * 创建订单
     * 1. 生成订单号
     * 2. Feign  获取场站的信息和设备信息(服务之间的调用  订单服务调用设备服务)
     * 3. Feign 获取用户信息(车辆信息) (服务之间的调用 订单服务调用用户服务)
     * 4. Feign 修改充电枪的状态 为 使用中 (服务之间的调用 订单服务调用设备服务)
     * 5. 物联网 给设备发送开始充电指令 (物联网 订单服务调用充电桩设备)
     * @param orderAddParam
     * @return
     */
    @Override
    public String createOrder(OrderAddParam orderAddParam) {
        //1 生成订单号
        String orderNo = getOrderNo();
        //2 获取场站的信息和设备信息
        StationInfoVO stationInfoVO = getStationInfoByGunId(orderAddParam.getGunId());
        //3 获取用户信息(车辆信息)
        UserInfoVO userInfoVO = getUserInfoByUserId(orderAddParam.getUserId());
        //4 修改充电枪的状态 为 使用中
        Boolean success = updateGunStatusBusy(orderAddParam.getGunId());

        //5 给设备发送开始充电指令
        startCharging(orderNo, orderAddParam.getPileId(), orderAddParam.getGunId());

        //6 创建设备自检任务
        log.debug("创建设备自检任务,入参:订单号{},枪id:{}",orderNo,orderAddParam.getGunId());
        try {
            DeviceCheckJob deviceCheckJob = new DeviceCheckJob(orderNo,orderAddParam.getGunId());
        } catch (SchedulerException e) {
            log.error("创建设备自检任务失败",e);
        }
        log.debug("创建设备自检任务,入参:订单号{},枪id:{}",orderNo,orderAddParam.getGunId());

        //7 延迟处理
        sendOrderMessage(orderNo);

        return orderNo;
    }

    @Autowired
    private RabbitMQOrderProducer rabbitMQOrderProducer;

    /**
     * 给RabbitMQ发送一条消息同时设置TTL,用来做延迟处理
     */
    private void sendOrderMessage(String orderNo){
        OrderMQPO orderMQPO = new OrderMQPO();
        orderMQPO.setOrderNo(orderNo);
        rabbitMQOrderProducer.sendOrder(orderMQPO);
    }

    @Autowired
    private MqttProducer mqttProducer;

    /**
     * 发送开始充电指令
     */
    private void startCharging(String orderNo,Integer pileId,Integer gunId) {
        //Topic 设计
        // 一种是枪的维度   或者 一种是桩的维度 消息体包含枪的信息
        // 从业务上讲都可以, 如果是枪的维度 topic的数量会比 桩的维度 的数量多
        // 占用更多的资源
        String topic = MqttConstants.TOPIC_START_CHARGING_PREFIX  + pileId;
        //消息的设计
        //类似于接口的入参,思考业务如果缺少哪个入参,业务无法完成,这些入参是就是核心参数
        //首先得明确业务目标  给设备发送开始充电指令给一个用户的订单开始充电,
        // 参数应该包含 订单信息的唯一标识 订单id,设备信息,告诉具体哪个设备开始充电
        ChargingDto chargingDto = new ChargingDto();
        chargingDto.setOrderNo(orderNo);
        chargingDto.setPileId(pileId);
        chargingDto.setGunId(gunId);
        chargingDto.setMsg(ChargingConstants.START_CHARGING);
        log.debug("订单服务发送开始充电指令到设备:{},消息:{}",topic,chargingDto);
        //String string = chargingDto.toString();
        //消息体的序列化 Spring-boot 自动做的序列化 对象转json ,json转对象
        //需要我们手动序列化,不能toString ,应该把  对象转换为JSON
        //需要一个库 Jackson(Spring默认的) Gson     Spring-boot怎么干的?
        String json = JsonUtils.toJson(chargingDto);
        Boolean success = mqttProducer.publish(topic, json);
        log.debug("订单服务发送开始充电指令到设备:{},消息:{},结果:{}",topic,chargingDto,success);
        //如果发送,重试几次,如果实在不行通知用户,记录日志,接入告警系统
    }

    /**
     * 更新枪的状态为 使用中
     * @param gunId
     * @return
     */
    private Boolean updateGunStatusBusy(Integer gunId) {
        GunStatusUpdateParam param = new GunStatusUpdateParam();
        param.setGunId(gunId);
        param.setStatus(ChargingConstants.GUN_STATUS_BUSY); //魔数 不能使用魔数,应该使用常量或者枚举等有业务含义的变量来表示
        JsonResult<Boolean> result = deviceClient.updateGunStatus(param);
        if (result != null) {
            return result.getData();
        }
        return false;
    }

    /**
     * 通过用户id获取用户信息
     * @param userId
     * @return
     */
    private UserInfoVO getUserInfoByUserId(Integer userId) {
        JsonResult<UserInfoVO> userCarInfo = userClient.getUserCarInfo(userId);
        if (userCarInfo != null) {
            return userCarInfo.getData();
        }
        return null;
    }

    /**
     * 通过枪id获取场站信息
     * @param gunId
     * @return
     */
    private StationInfoVO getStationInfoByGunId(Integer gunId){
        JsonResult<StationInfoVO> stationInfo = deviceClient.getStationInfo(gunId);
        if (stationInfo != null) {
            return stationInfo.getData();
        }
        return null;
    }


    /**
     * 生成订单号
     * 唯一的
     * 1 自增的数据库主键   分库分表不能保证唯一  会重复
     * 2 UUID 随机并且唯一
     *   UUID能当数据库的主键么? mysql innodb 注解索引的实现有关系 todo
     *
     * 分布式id生成器  分布式场景下能 高性能的 生成唯一 尽量无规则 的id
     * 雪花算法 https://www.jianshu.com/p/1af94260664a
     * 美团  https://tech.meituan.com/2017/04/21/mt-leaf.html
     *
     * 现在业务刚开始 先自己生成
     *
     * 固定开始
     * 10000 + 随机数 + 时间戳
     *
     *
     * @return
     */
    private String getOrderNo(){
        String start = "10000";
        Random random = new Random();
        int r = random.nextInt(1000);
        long now = System.currentTimeMillis();
        String orderNo = start + "_" + r + "_" + now;
        return orderNo;
    }


}
